﻿using System;
using MugenInjection.Delegates;
using System.Collections.Generic;

namespace MugenInjection.Infrastructure
{
    internal sealed class CacheCollection<T, TValue> where T : class
    {
        #region Nested type

        private struct CacheTuple
        {
            public T Type;

            public TValue Value;
        }

        #endregion

        #region Fields

        internal readonly Dictionary<T, TValue> Cache;
        private readonly CacheTuple[] _cacheList;
        private readonly int _maxCount;
        private int _currentCount;
        private int _currentCountActual;

        #endregion

        #region Constructor

        /// <summary>
        ///     Initializes a new instance of the <see cref="CacheCollection{T,TValue}" /> class.
        /// </summary>
        public CacheCollection()
            : this(10)
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="CacheCollection{T,TValue}" /> class.
        /// </summary>
        public CacheCollection(int countCollectionCache)
            : this(countCollectionCache, null)
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="CacheCollection{T,TValue}" /> class.
        /// </summary>
        public CacheCollection(int countCollectionCache, IEqualityComparer<T> equalityComparer)
        {
            Cache = equalityComparer == null
                        ? new Dictionary<T, TValue>()
                        : new Dictionary<T, TValue>(equalityComparer);
            _maxCount = countCollectionCache - 1;
            _cacheList = new CacheTuple[countCollectionCache];
        }

        #endregion

        #region Properties

        public TValue this[T key]
        {
            get
            {
                for (int index = 0; index < _currentCountActual; index++)
                {
                    CacheTuple cacheTuple = _cacheList[index];
                    if (cacheTuple.Type != key) continue;
                    return cacheTuple.Value;
                }
                TValue res;
                if (Cache.TryGetValue(key, out res))
                    _cacheList[GetCurrent()] = new CacheTuple { Type = key, Value = res };
                return res;
            }
            set
            {
                Remove(key);
                Cache[key] = value;
            }
        }

        #endregion

        #region Methods

        public void Add(T key, TValue value)
        {
            this[key] = value;
        }

        public TValue GetOrAdd(T key, Func<TValue> funcValue)
        {
            for (int index = 0; index < _currentCountActual; index++)
            {
                CacheTuple cacheTuple = _cacheList[index];
                if (cacheTuple.Type != key) continue;
                return cacheTuple.Value;
            }
            TValue res;
            if (!Cache.TryGetValue(key, out res))
            {
                res = funcValue();
                Cache[key] = res;
            }
            _cacheList[GetCurrent()] = new CacheTuple { Type = key, Value = res };
            return res;
        }

        public void Remove(T key)
        {
            Cache.Remove(key);
            for (int i = 0; i < _cacheList.Length; i++)
            {
                if (_cacheList[i].Type != key) continue;
                _cacheList[i] = new CacheTuple();
            }
        }

        private int GetCurrent()
        {
            if (_currentCount > _maxCount)
                _currentCount = 0;
            if (_currentCountActual != _maxCount)
                _currentCountActual++;
            return _currentCount++;
        }

        public void Clear()
        {
            for (int i = 0; i < _cacheList.Length; i++)
                _cacheList[i] = new CacheTuple();
            Cache.Clear();
        }

        #endregion
    }
}